"""Displays various statistics for the pattern database.

Juggle Analyze presents two types of tables: one presenting average grades
and the other summarizing grades collected in the database. Both tables have
several sorting options (see the display's footer).

All input forms are case insensitive.

TYPES OF WARNINGS IN THE AVERAGE GRADES SUMMARY:
! - the number of grades for the pattern is lower than trust_thresh (defined
     in options.cfg) and therefore it should be viewed as unreliable.

"""

import string
import csv
import os
from operator import itemgetter
import spoldzielnia

avg_grades_list = []
grades_ints_list = []
warnings_list = []
ones_list = []
twos_list = []
threes_list = []
fours_list = []
fives_list = []
total_list = []    

# sets options with spoldzielnia.py module. 'opcje' is a tuple with options returned by the module.
trust_thresh = spoldzielnia.konfigurator("analyze")

# defines lists with spoldzielnia.py module. 'listy' is a tuple with lists returned by the module.
listy = spoldzielnia.czytacz()
trick_list = listy[0]
grades_string_list = listy[1]
tag_string_list = listy[2]
del listy

def usredniacz():
    """Calculates average grades and performs various checks.

    The average grades are calculated with two positions after point.
    Moreover, the function checks whether the number of grades for a pattern
    is not too small (defined by trust_thresh in options.cfg) Eventually, 
    the function returns a zipped list ready for sorting.
    """

    for zlicz in range(0, len(trick_list)):
        # in case of dividing by 0...
        try:
            avg_grade = round(sum(grades_ints_list[zlicz]) / len(grades_ints_list[zlicz]), 2)
        except ZeroDivisionError:
            avg_grade = 0

        avg_grades_list.append(avg_grade)

        # additional checks: is the number of grades unreliable (based of trust_thresh option)?
        if len(grades_ints_list[zlicz]) < trust_thresh:
            warnings_list[zlicz] = warnings_list[zlicz] + too_few_msg
        else:
            pass
        
    # Lists are zipped to avoid mess while sorting one list without sorting the other lists.
    zipped_list = zip(trick_list, avg_grades_list, warnings_list)
    return zipped_list


def sumer():
    """Calculates data for database statistics.

    The statistics include: sum of every kind of grade for pattern and
    sum of all grades for a pattern. Returns a zipped list ready for sorting.
    """

    # These lists are globalized, as they have to be used in print_stats()
    global ones_list, twos_list, threes_list, fours_list, fives_list, total_list
    
    # returns integers representing number of grades in grade strings and sums grades
    for zlicz in range(0, len(trick_list)):
        ones = grades_string_list[zlicz].count("1")
        ones_list.append(ones)
        twos = grades_string_list[zlicz].count("2")
        twos_list.append(twos)
        threes = grades_string_list[zlicz].count("3")
        threes_list.append(threes)
        fours = grades_string_list[zlicz].count("4")
        fours_list.append(fours)
        fives = grades_string_list[zlicz].count("5")
        fives_list.append(fives)
        total = len(grades_string_list[zlicz])
        total_list.append(total)
        # Lists are zipped to avoid mess while sorting one list without sorting the other lists.

    zipped_list_stats = zip(trick_list, ones_list, twos_list, threes_list, fours_list, fives_list, total_list)
    return zipped_list_stats


def print_avgs():
    """Prints sorted list to screen in the form of a table.
    """
    
    os.system(['clear','cls'][os.name == 'nt'])
 
    # First, figure out how to break the list into 2 columns.
    if len(trick_list) % 2:
        col_length = int(len(trick_list) / 2) + 1
        sorted_list.append(["soon to come...", " ", " "])
    else:
        col_length = int(len(trick_list) / 2)

    print "------------------------------------------------------------------------------"
    print ""
    print string.center("Table sorted by AVERAGE GRADE", 77)
    print ""
    for zlicz in range(0, col_length):
        col1_tuple = sorted_list[zlicz]
        col2_tuple = sorted_list[zlicz + col_length]
        print " " + string.ljust(col1_tuple[0].strip(), 30) + string.ljust(str(col1_tuple[1]), 4) + string.ljust(col1_tuple[2], 2) + "    " + string.ljust(col2_tuple[0].strip(), 30) + string.ljust(str(col2_tuple[1]), 4) + string.ljust(col2_tuple[2], 2)
    print ""
    print "------------------------------------------------------------------------------"


def print_stats():
    """Prints statistics of the database to screen in the form of a table.

    This function also calculates the last row od the table: sums of all types
    of grades in the database and sum of all grades in the database.
    """

    os.system(['clear','cls'][os.name == 'nt'])
    print "------------------------------------------------------------------------------"
    print ""
    print string.center("Pattern statistics", 74)
    print ""
    if analysis_mode == "az":
        print string.center(str(len(trick_list)) + " patterns, sorted ALPHABETICALLY", 74)
    elif analysis_mode == "1":
        print string.center(str(len(trick_list)) + " patterns, sorted by 1s", 74)
    elif analysis_mode == "2":
        print string.center(str(len(trick_list)) + " patterns, sorted by 2s", 74)
    elif analysis_mode == "3":
        print string.center(str(len(trick_list)) + " patterns, sorted by 3s", 74)
    elif analysis_mode == "4":
        print string.center(str(len(trick_list)) + " patterns, sorted by 4s", 74)
    elif analysis_mode == "5":
        print string.center(str(len(trick_list)) + " patterns, sorted by 5s", 74)
    else:
        print string.center(str(len(trick_list)) + " patterns, sorted by ALL PATTERN'S GRADES", 74)
    print ""
    print string.rjust("Pattern", 27) + "   " + string.center("1s", 5) + string.center("2s", 5) + string.center("3s", 5) + string.center("4s", 5) + string.center("5s", 5) + "   " + string.center("Total", 5)
    print ""

    # prints the actual table
    for zlicz in range(0, len(trick_list)):
        current_tuple = sorted_list_stats[zlicz]
        print string.rjust(current_tuple[0].strip(), 27) + "   " + string.center(str(current_tuple[1]), 5) + string.center(str(current_tuple[2]), 5) + string.center(str(current_tuple[3]), 5) + string.center(str(current_tuple[4]), 5) + string.center(str(current_tuple[5]), 5) + "   " + string.center(str(current_tuple[6]), 5)
    print ""
    print string.rjust("Grades in total:", 27) + "   " + string.center(str(sum(ones_list)), 5) + string.center(str(sum(twos_list)), 5) + string.center(str(sum(threes_list)), 5) + string.center(str(sum(fours_list)), 5) + string.center(str(sum(fives_list)), 5) + "   " + string.center(str(sum(total_list)), 5)
    print ""
    print "------------------------------------------------------------------------------"


def tag_counta():
    """Calculates the number of patterns tagged with each tag.
    """

    # Creates the list of unique tags.
    all_tags_string = ""
    for zlicz in range(0, len(trick_list)):
        all_tags_string = all_tags_string + " " + tag_string_list[zlicz].lower()
    unique_tags_aux = list(set(all_tags_string.split()))
    unique_tags_list = sorted(unique_tags_aux)

    os.system(['clear','cls'][os.name == 'nt'])
    print "------------------------------------------------------------------------------"
    print ""
    print string.center("Tag statistics", 39)
    print ""
    print string.rjust("Tag", 15) + "   " + string.center("Count", 5) + "   " + string.center("% of patterns", 13)
    print ""

    # Calcultes and prints output.
    for x in range(0, len(unique_tags_list)):
        current_tag_count = all_tags_string.count(unique_tags_list[x])
        current_tag_percent = float(current_tag_count) / float(len(trick_list)) * 100
        print string.rjust(unique_tags_list[x], 15) + "   " + string.center(str(current_tag_count), 5) + "   " + string.center(str(int(current_tag_percent)) + " %", 13)

    print ""
    print "------------------------------------------------------------------------------"

def calc_amplitude():
    """Calculates the amplitude of grades.
    """

    amplitude_list = []

    # Calculates amplitude of grades.
    # If number of grades < 5, the data is not considered reliable.
    for zlicz in range(0, len(trick_list)):
        try:
            if len(grades_string_list[zlicz]) > 4: 
                current_ampl = (int(max(grades_string_list[zlicz])) - int(min(grades_string_list[zlicz])))
            else:
                current_ampl = "n/a"
        except ValueError:
            current_ampl = "n/a"
        amplitude_list.append(str(current_ampl))

    # Sorting (lists are zipped to prevent mismatch while sorting the ratings and not the names of tricks).
    sorted_amplitude = sorted(zip(trick_list, amplitude_list), key=itemgetter(1), reverse=True)
    
    # First, figure out how to break the list into 2 columns.
    if len(trick_list) % 2:
        col_length = int(len(trick_list) / 2) + 1
        sorted_amplitude.append(["soon to come...", " "])
    else:
        col_length = int(len(trick_list) / 2)

    print "-----------------------------------------------------------------------------"
    print ""
    print string.center("Amplitude of grades", 77)
    print ""

    # Prints the results.
    for zlicz in range(0, col_length):
        col1_amplitude = sorted_amplitude[zlicz]
        col2_amplitude = sorted_amplitude[zlicz + col_length]
        print " " + string.ljust(col1_amplitude[0], 30) + string.ljust(col1_amplitude[1], 3) + "    " + string.ljust(col2_amplitude[0], 30) + string.ljust(col2_amplitude[1], 3)

    print ""
    print "-----------------------------------------------------------------------------"

def wykres():
    """ Draws a diagram that plots the grades for a single pattern on a diagram.
    """
    
    import turtle
    
    # defines lists with spoldzielnia.py module. 'listy' is a tuple with lists returned by the module.
    listy = spoldzielnia.czytacz()
    trick_list = listy[0]
    grades_string_list = listy[1]
    del listy
    
    # trick selection prompt
    # First, figure out how to break the list into 2 columns.
    zipped_tuple = zip(trick_list, grades_string_list)
    sorted_tricks = sorted(zipped_tuple, key=itemgetter(0), reverse=False)
    if len(trick_list) % 2:
        col_length = int(len(trick_list) / 2) + 1
        sorted_tricks.append(["soon to come...", ""])
    else:
        col_length = int(len(trick_list) / 2)
    print ""
    print "Available patterns:"
    print "+++++++++++++++++++"
    print ""
    
    for zlicz in range(0, col_length):
        # calculates the numeral in the second column for the current line.
        col2_index = zlicz + col_length
        print string.rjust(str(zlicz), 3) + " " + string.ljust(sorted_tricks[zlicz][0], 35) + "  " + string.rjust(str(col2_index), 3) + " " + string.ljust(sorted_tricks[col2_index][0], 35)
    print ""
    which_trick = raw_input(" Choose a pattern: ")
    
    # Validates input; is it a digit from the accepted range?
    while which_trick.isdigit() == False or int(which_trick) not in range(0, len(trick_list)):
        which_trick = raw_input("  * Oops! No such pattern on the list! One more time: ")
    
    trick_numb = int(which_trick)
    
    # Checks if the grades are drawable (are there enough of them?).
    if len(sorted_tricks[trick_numb][1]) < 3:
        print ""
        print "  * Not enough data to draw anything reasonable."
        print ""
    else:
        turtle.speed(10)
        turtle.hideturtle()
        turtle.up()
    
        # draws the heading
        turtle.goto(0, 150)
        turtle.write(sorted_tricks[trick_numb][0] + ", " + str(len(sorted_tricks[trick_numb][1])) + " grades", align="center")
    
        # draws axes
        turtle.goto(-250, 100)
        turtle.down()
        turtle.left(90)
        turtle.stamp()
        turtle.left(180)
        turtle.forward(50)
        turtle.write("5", align="right")
        turtle.forward(50)
        turtle.write("4", align="right")
        turtle.forward(50)
        turtle.write("3", align="right")
        turtle.forward(50)
        turtle.write("2", align="right")
        turtle.forward(50)
        turtle.write("1", align="right")
        turtle.left(90)
        turtle.forward(500)
        turtle.stamp()
        turtle.up()
        turtle.goto(-250, -10)
        turtle.down()
        turtle.color("green")
        turtle.forward(500)
        turtle.color("black")
        turtle.up()
        
    
        # drawing the grades
        current_grades = sorted_tricks[trick_numb][1]
    
        # calculate step on x-axis
        x_step = 500 / len(current_grades)
    
        # sets up the initial position
        if current_grades[0] == "1":
            start_igrek = -150
        elif current_grades[0] == "2":
            start_igrek = -100
        elif current_grades[0] == "3":
            start_igrek = -50
        elif current_grades[0] == "4":
            start_igrek = 0
        else:
            start_igrek = 50
    
        turtle.setpos(-250, start_igrek)
        turtle.dot()
    
        turtle.down()
        for zlicz in range(1, len(current_grades)):
            if current_grades[zlicz] == "1":
                igrek = -150
            elif current_grades[zlicz] == "2":
                igrek = -100
            elif current_grades[zlicz] == "3":
                igrek = -50
            elif current_grades[zlicz] == "4":
                igrek = 0
            else:
                igrek = 50
    
            iks = turtle.xcor() + x_step
            turtle.goto(iks, igrek)
            turtle.dot()
    
    raw_input("Press any key...")
    turtle.clear()
    print ""
    print "-----------------------------------------------------------------------------"

# constructs warning message and warnings_list(possible only after lists are available to assess list length)
too_few_msg = "!"
for i in range(0, len(trick_list)):
    warnings_list.append(" ")

# converts lists; required for further computation
for zlicz in range(0, len(trick_list)):
    grades_aux_ints = [float(y) for y in list(grades_string_list[zlicz])]
    grades_ints_list.append(grades_aux_ints)

# Takes the processed lists out of usredniacz() and sumer()
zipped_list = usredniacz()
zipped_list_stats = sumer()

analysis_mode = "g"
while analysis_mode != "x":
    if analysis_mode == "g":
        sorted_list = sorted(zipped_list, key=itemgetter(1), reverse=True)
        print_avgs()
    elif analysis_mode == "z":
        sorted_list_stats = sorted(zipped_list_stats, key=itemgetter(0), reverse=False)
        print_stats()
    elif analysis_mode == "1":
        sorted_list_stats = sorted(zipped_list_stats, key=itemgetter(1), reverse=True)
        print_stats()
    elif analysis_mode == "2":
        sorted_list_stats = sorted(zipped_list_stats, key=itemgetter(2), reverse=True)
        print_stats()
    elif analysis_mode == "3":
        sorted_list_stats = sorted(zipped_list_stats, key=itemgetter(3), reverse=True)
        print_stats()
    elif analysis_mode == "4":
        sorted_list_stats = sorted(zipped_list_stats, key=itemgetter(4), reverse=True)
        print_stats()
    elif analysis_mode == "5":
        sorted_list_stats = sorted(zipped_list_stats, key=itemgetter(5), reverse=True)
        print_stats()
    elif analysis_mode == "a":
        sorted_list_stats = sorted(zipped_list_stats, key=itemgetter(6), reverse=True)
        print_stats()
    elif analysis_mode == "t":
        tag_counta()
    elif analysis_mode == "m":
        calc_amplitude()
    elif analysis_mode == "d":
        wykres()
    else:
        analysis_mode = "g"
    # Prints footer menu
    print ""
    print "    " + string.ljust("[g] Sort by AVERAGE GRADE", 40) + string.ljust("[m] Amplitude of grades", 30)
    print "    " + string.ljust("[a] Sort by NUMBER OF ALL GRADES", 40)  + string.ljust("[t] Count tags", 30)
    print "  " + string.ljust("[1-5] Sort by GRADE...", 40)  + string.ljust("  [d] Draw a diagram", 30)
    print "    [a] Sort by NUMBER OF ALL GRADES"
    print "    " + string.ljust("[z] Sort ALPHABETICALLY", 40)  + string.ljust("[x] Quit", 30)
    analysis_mode = raw_input("? ").lower()
    while analysis_mode not in ["z", "a", "1", "2", "3", "4", "5", "g", "t", "m", "d", "x"]:
        analysis_mode = raw_input("   Oops! This is not a valid keystroke. Let's try again... ").lower()
